package com.guokai2012.util;

import com.alibaba.fastjson.JSON;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Client;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.FileInfo;
import com.qiniu.util.Auth;
import com.qiniu.util.StringMap;
import com.qiniu.util.StringUtils;

import java.io.*;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author JH012021
 * @ClassName FileUpDownQiNiuOSS
 * @Date 2021/8/25 13:25
 * @Description 七牛文件上传下载
 */
public class FileUpDownQiNiuOss {
    private final double blobIo;
    private final double space;
    private final Auth auth;
    private final PropertiesManager proManager;

    /**
     * 构造方法
     */
    private FileUpDownQiNiuOss(PropertiesManager propertiesManager) throws IllegalArgumentException {
        this.proManager = propertiesManager;
        this.blobIo = proManager.getBlobIo() * 1048576;
        this.space = proManager.getSpace() * 1048576;

        if (StringUtils.isNullOrEmpty(proManager.getAk()) || StringUtils.isNullOrEmpty(proManager.getSk())) {
            throw new IllegalArgumentException("empty AK or SK");
        }
        if (StringUtils.isNullOrEmpty(proManager.getDownUrl())) {
            throw new IllegalArgumentException("empty down url");
        }
        if (StringUtils.isNullOrEmpty(proManager.getBucket())) {
            throw new IllegalArgumentException("empty bucket");
        }
        this.auth = Auth.create(proManager.getAk(), proManager.getSk());
    }

    /**
     * 单例
     */
    public static FileUpDownQiNiuOss create(PropertiesManager propertiesManager) {
        FileUpDownQiNiuOss obj = null;
        try {
            obj = new FileUpDownQiNiuOss(propertiesManager);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }
        return obj;
    }

    /**
     * 上传文件
     */
    public Map<String, Object> upLoadFile(String path) {
        Map<String, Object> map = new HashMap<String, Object>() {{
            put("code", 200);
            put("message", "");
        }};
        try {
            File file = new File(path);
            if (file.length() < 30) {
                System.out.println("302 -> 文件为空，无需上传");
                return map;
            }
            double space = getSpace();
            if ((space + file.length()) >= this.space) {
                // 如果 线上已使用存储空间+待存储文集大小 > 设定阈值，不上传
                map.put("code", 401);
                map.put("message", "存储空间已达上限设置");
                return map;
            }
            String key = file.getName();
            String md5 = getMD5Three(file);
            FileInfo fileMetadata = getQiNiuFileMetadata(key);
            if (md5.equals(fileMetadata.md5)) {
                //  如果线上文本于本地文件一直，则无需上传
                System.out.println("云上文本于本地文件一直，无需上传");
                return map;
            }
            String token = auth.uploadToken(proManager.getBucket(), key);
            UploadManager uploadManager = new UploadManager(new Configuration());
            uploadManager.put(file, key, token);
        } catch (QiniuException ex) {
            //  e.printStackTrace();
            map.put("code", ex.response.statusCode);
            map.put("message", ex.response.error);
        } catch (Exception e) {
            //  e.printStackTrace();
            map.put("code", 500);
            map.put("message", "上传错误，请检查七牛配置信息");
        }
        return map;
    }

    /**
     * 下载文件
     */
    public boolean getFile(File file) {
        FileWriter out = null;
        try {
            double blobIo = getBlobIo();
            if (blobIo >= this.blobIo) {
                // 已使用下行流量超过 设定阈值，则停止下载
                return false;
            }
            String key = file.getName();
            String md5 = getMD5Three(file);

            FileInfo fileMetadata = getQiNiuFileMetadata(key);
            if (md5.equals(fileMetadata.md5)) {
                //  本地文件于线上文件md5一致，无需下载
                return false;
            }
            String url = auth.privateDownloadUrl(proManager.getDownUrl() + "/" + key);
            StringBuilder builder = downFileByUrl(url);
            out = new FileWriter(file);
            out.write(builder.toString());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (null != out) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 检查 Ak 或者 Sk 是否正确
     */
    public Map<String, Object> checkQiNiuExe(String fileName) {
        Map<String, Object> map = new HashMap<String, Object>() {{
            put("code", 200);
            put("message", "");
        }};
        try {
            double space = getSpace();
            if (space >= this.space) {
                map.put("code", 401);
                map.put("message", "已达存储空间上限！");
                return map;
            }
            double blobIo = getBlobIo();
            if (blobIo >= this.blobIo) {
                map.put("code", 402);
                map.put("message", "已达下行流量上限！");
                return map;
            }
        } catch (QiniuException e) {
            e.printStackTrace();
            //  ak 或 sk 不能使用
            map.put("code", e.code());
            if (e.code() == -1) {
                map.put("code", 502);
                map.put("message", "网络不可用，是否重新尝试连接？\nTip：请先检查网络后再尝试连接。");
            } else if (e.code() == 401) {
                map.put("message", "AK或SK无效！");
            } else {
                map.put("message", e.error());
            }
            return map;
        }

        try {
            FileInfo fileInfo = getQiNiuFileMetadata(fileName);
            if (!StringUtils.isNullOrEmpty(fileInfo.md5)) {
                //  文件存在，则接着 验证 下载连接是否正常
                try {
                    String url = auth.privateDownloadUrl(proManager.getDownUrl() + "/" + fileName);
                    downFileByUrl(url);
                } catch (IOException e) {
                    map.put("code", 403);
                    map.put("message", "下载连接不正确！");
                }
            }
        } catch (QiniuException e) {
            map.put("code", e.response.statusCode);
            map.put("message", e.response.error);
            if (631 == e.response.statusCode) {
                map.put("message", "设置的BUCKET不存在，请检查是否设置错误！");
            }
            if (612 == e.response.statusCode) {
                //  这个是文件不存，可能是第一启动，没有上传文件。所以直接判定为能使用
                map.put("message", "tipData文件不存在！");
            }
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 获取文件 Meta信息
     */
    private FileInfo getQiNiuFileMetadata(String key) throws QiniuException {
        BucketManager bucketManager = new BucketManager(auth, new Configuration());
        try {
            return bucketManager.stat(proManager.getBucket(), key);
        } catch (QiniuException e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 通过 私有空间下载连接，下载文件
     */
    private StringBuilder downFileByUrl(String url) throws IOException {
        InputStreamReader in = null;
        StringBuilder result = new StringBuilder();
        try {
            URL realUrl = new URL(url);
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            // 建立连接
            connection.connect();
            // 用字符输入流来 读取URL的响应
            in = new InputStreamReader(connection.getInputStream());
            char[] chars = new char[1024];
            int readCount;
            while ((readCount = in.read(chars)) != -1) {
                result.append(chars, 0, readCount);
            }
        } catch (IOException e) {
            throw e;
        } finally {
            try {
                if (null != in) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 已用 存储空间统计
     */
    private double getSpace() throws QiniuException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd000000");
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        String begin = sdf.format(calendar.getTime());
        calendar.add(Calendar.MONTH, 1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        String end = sdf.format(calendar.getTime());
        // 获取 bucket 的使用 量
        String url = "http://api.qiniu.com/v6/space?begin=" + begin + "&end=" + end + "&g=day&bucket=" + proManager.getBucket();
        Response resp = usedCountRequest(url);
        return JSON.parseObject(resp.bodyString()).getJSONArray("datas").getDouble(0);
    }

    /**
     * 已用 下行流量统计
     */
    private double getBlobIo() throws QiniuException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd000000");
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        String begin = sdf.format(calendar.getTime());
        calendar.add(Calendar.MONTH, 1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        String end = sdf.format(calendar.getTime());
        // 获取 bucket 的使用 量
        String url = "http://api.qiniu.com/v6/blob_io?begin=" + begin + "&end=" + end + "&g=day&select=flow";
        Response resp = usedCountRequest(url);
        return JSON.parseArray(resp.bodyString()).getJSONObject(0).getJSONObject("values").getDouble("flow");
    }

    /**
     * 七牛统计API方法封装
     */
    private Response usedCountRequest(String url) throws QiniuException {
        String authorization = "Qiniu " + auth.signQiniuAuthorization(url, "GET", null, "application/x-www-form-urlencoded");
        Client client = new Client();
        StringMap headers = new StringMap();
        headers.put("Authorization", authorization);
        headers.put("Content-Type", "application/x-www-form-urlencoded");
        try {
            return client.get(url, headers);
        } catch (QiniuException e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 计算文件md5
     */
    private static String getMD5Three(File file) {
        BigInteger bi = null;
        try {
            byte[] buffer = new byte[8192];
            int len = 0;
            MessageDigest md = MessageDigest.getInstance("MD5");
            FileInputStream fis = new FileInputStream(file);
            while ((len = fis.read(buffer)) != -1) {
                md.update(buffer, 0, len);
            }
            fis.close();
            byte[] b = md.digest();
            bi = new BigInteger(1, b);
        } catch (NoSuchAlgorithmException | IOException e) {
            e.printStackTrace();
        }
        return bi.toString(16);
    }
/*
    public static void main(String[] args) {
        String s = Objects.requireNonNull(FileUpDownQiNiuOss.class.getClassLoader().getResource("")).getPath() + "config/todoList.properties";
        PropertiesManager manager = new PropertiesManager(s);
        FileUpDownQiNiuOss qiNiuOss = FileUpDownQiNiuOss.create(manager);
        String s1 = Objects.requireNonNull(TodoListMain.class.getClassLoader().getResource("")).getPath() +
                "tmpfile/tipData.txt";
        File file = new File(s1);
        Map<String, Object> map = qiNiuOss.checkQiNiuExe(file.getName());
        System.out.println(map);
    }*/
}


